import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom'
import 'styles/components/hover-content.scss'

/**
 * 鼠标hover时显示的额外内容
 * 但是跟Tooltip比少了很多东西
 * 1：Tooltip可以无限叠加，这一点我看了源码还没弄清楚怎么搞的
 * 2：我这里使用的是relatedTarget判断鼠标是否在组件内，但是Tooltip没有用这个东西
 *    看了源码好像是把onMouseEnter和onMouseLeave无限传递了下去实现的
 *    这就导致Tooltip只用一个popupVisible state变量就可以控制是否显示，也可以设置delayTimer
 */
class HoverContent extends Component {

  container = null;
  renderedChildren = null;
  mouseInComponent = false;
  relatedTarget = null;
  popupRendered = false;

  createChainedFunction() {
    let args = arguments;
    return function chainedFunction() {
      for (let i = 0; i < args.length; i++) {
        if (args[i] && args[i].apply) {
          args[i].apply(this, arguments);
        }
      }
    };
  }

  constructor(props) {
    super(props);
    let visible = void 0;
    if('visible' in props)
      visible = !!props.visible;
    else
      visible = !!props.defaultVisible;
    this.state = {
      visible
    }
  }

  componentDidMount() {
    const { getPopupContainer, disabled } = this.props;
    if(!disabled) {
      this.renderedChildren = ReactDOM.findDOMNode(this);
      let popupContainer = getPopupContainer(this.renderedChildren);
      this.container = document.createElement('div');
      this.container.className = 'hover-content-container';
      popupContainer.append(this.container);
    }
  }

  renderContainer = () => {
    const { placement, content, wrapperClassName, destroyOnHide, iconStyle } = this.props;
    const { visible } = this.state;
    let childrenRect = this.renderedChildren.getBoundingClientRect();
    let containerRect = this.container.getBoundingClientRect();
    let [offsetX, offsetY] = [childrenRect.left - containerRect.left, childrenRect.top - containerRect.top];
    let wrapperStyle = {
      position: 'absolute',
      left: `${offsetX + placement.left(childrenRect)}px`,
      top: `${offsetY + placement.top(childrenRect)}px`
    };
    let contentWrapper = React.createElement(
      'div',
      {
        className: this.classNames(
          [
            'hover-content-wrapper',
            { [wrapperClassName]: wrapperClassName },
            { 'hover-content-wrapper-hide': !visible }
          ]
        ),
        style: { ...wrapperStyle, ...iconStyle },
        onMouseEnter: this.onMouseEnter,
        onMouseLeave: this.onMouseLeave
      },
      content
    );
    if(destroyOnHide) {
      ReactDOM.unstable_renderSubtreeIntoContainer(
        this,
        visible ? contentWrapper : <span />,
        this.container
      );
    } else {
      ReactDOM.unstable_renderSubtreeIntoContainer(
        this,
        contentWrapper,
        this.container
      );
    }
  };

  componentWillUnmount () {
    const { getPopupContainer, disabled } = this.props;
    if(!disabled) {
      try {
        this.renderedChildren = ReactDOM.findDOMNode(this);
        let popupContainer = getPopupContainer(this.renderedChildren);
        ReactDOM.unmountComponentAtNode(this.container);
        popupContainer.removeChild(this.container);
      }catch (e) {
      }
    }
  }

  componentWillReceiveProps(nextProps) {
    if('visible' in nextProps) {
      this.setState({ visible: !!nextProps.visible });
    }
    // 解决 disabled 由 true 变为 false 时，container 和 renderedChildren 为 null 的情况
    if (!nextProps.disabled && this.props.disabled) {
      this.renderedChildren = ReactDOM.findDOMNode(this);
      let popupContainer = nextProps.getPopupContainer(this.renderedChildren);
      this.container = document.createElement('div');
      this.container.className = 'hover-content-container';
      popupContainer.append(this.container);
    }
  }

  componentDidUpdate() {
    const { disabled } = this.props;
    if(this.popupRendered && !disabled)
      this.renderContainer();
  }

  setPopupVisible = visible => {
    if(this.state.visible !== visible) {
      if(!('visible' in this.props)) {
        this.setState({ visible });
        const { onVisibleChange } = this.props;
        onVisibleChange && onVisibleChange(visible);
      }
    }
  };

  onMouseEnter = e => {
    const { visible } = this.state;
    if(!this.mouseInComponent && !visible) {
      this.setPopupVisible(true);
    }
    this.mouseInComponent = true;
  };

  onMouseLeave = e => {
    this.mouseInComponent = false;
    this.relatedTarget = e.relatedTarget;
    setTimeout(() => {
      try {
        if(!this.mouseInComponent && !this.container.contains(this.relatedTarget)) {
          this.setPopupVisible(false);
        }
      } catch(e) {
        console.log(e);
      }
    }, 200);
  };

  render() {
    const { children, disabled } = this.props;
    this.popupRendered = this.popupRendered || this.state.visible;
    let child = React.isValidElement(children) ? children : React.createElement('span', null, children);
    let childProps = child.props || {};
    return disabled ? child : React.cloneElement(
      child,
      {
        onMouseEnter: this.createChainedFunction(this.onMouseEnter, childProps.onMouseEnter),
        onMouseLeave: this.createChainedFunction(this.onMouseLeave, childProps.onMouseLeave)
      }
    )
  }

  static propTypes = {
    getPopupContainer: PropTypes.func,
    placement: PropTypes.object,
    onVisibleChange: PropTypes.func,
    visible: PropTypes.bool,
    destroyOnHide: PropTypes.bool,
    iconStyle: PropTypes.object
  };

  static defaultProps = {
    getPopupContainer: triggerNode => triggerNode.parentNode,
    placement: {
      left: () => 0,
      top: () => 0
    }
  };

}

export default HoverContent;
